I found this to be working... is there a simpler way to achieve this?
This takes the translation3D we get from the drag gesture, records an initialOffset (otherwise my entity jumps upward, because it has its origin in the bottom center). then it rotates the translation vector around x such that y becomes 0. Finally it converts it into the coordinate system of the entity's parent.
Phew...
.gesture(DragGesture()
.targetedToAnyEntity()
.onChanged { value in
if initialDragOffset == .zero {
// Calculate the initial drag offset at the beginning of the drag
let startLocation = value.convert(value.startLocation3D, from: .local, to: value.entity.parent!)
initialDragOffset = value.entity.position - SIMD3<Float>(startLocation.x, startLocation.y, startLocation.z)
}
let translation3D = value.translation3D
// Rotate the drag vector such that its y-component becomes zero
let theta = atan2(translation3D.y, translation3D.z)
let cosTheta = cos(theta)
let sinTheta = sin(theta)
let rotatedX = translation3D.x
let rotatedY = 0 // Force y to be zero
let rotatedZ = theta >= 0 ? translation3D.y * sinTheta + translation3D.z * cosTheta : translation3D.z * cosTheta - translation3D.y * sinTheta
let rotatedVector = SIMD3<Float>(Float(rotatedX), 0, Float(rotatedZ))
var newLocation3D = value.startLocation3D
newLocation3D.x += Double(rotatedVector.x)
newLocation3D.y += Double(rotatedVector.y)
newLocation3D.z += Double(rotatedVector.z)
// Convert the 3D location of the drag gesture to the entity's parent coordinate space
let dragLocation = value.convert(newLocation3D, from: .local, to: value.entity.parent!)
var newPosition = SIMD3<Float>(dragLocation.x, dragLocation.y, dragLocation.z) + initialDragOffset
// don't modify y coordinate so that model remains standing on ground
newPosition.y = value.entity.position.y
// Set the entity's position to the new position
value.entity.position = newPosition
}
.onEnded({ value in
initialDragOffset = .zero
}))